﻿
using EmperialApps.WeatherSpark.Data;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace EmperialApps.WeatherSpark {

    /// <summary>Allows selection of different levels of detail for a data set in a forecast.</summary>
    public partial class DisplaySelector : UserControl {

        partial void InitializeInstance( ) {
            if( DesignerProperties.IsInDesignTool )
                return;

            InitializeComponent( );

            var displayLevelConverter = new DisplayLevelConverter( this );
            var displayLevelBinding = new Binding { Source = this, Path = new PropertyPath( "DisplayLevel" ), Mode = BindingMode.TwoWay, Converter = displayLevelConverter };
            this.Selector.SetBinding( DisplaySelectorSlider.ValueProperty, displayLevelBinding );
        }


        /// <summary>Gets or sets the allowed display values.</summary>
        public ForecastDisplayLevel Mask { get; set; }


        partial void LimitChanged( double oldValue ) {
            this.Selector.Limit = this.Limit;
        }

        private void OnDisplayLevelChanged( ForecastDisplayLevel oldLevel, ForecastDisplayLevel newLevel ) {
            var e = new RoutedPropertyChangedEventArgs<ForecastDisplayLevel>( oldLevel, newLevel );
            this.OnDisplayLevelChanged( e );
        }


        /// <summary>Converts between enum and numeric display level values.</summary>
        private sealed class DisplayLevelConverter : IValueConverter {
            private readonly DisplaySelector _selector;

            public DisplayLevelConverter( DisplaySelector selector ) { this._selector = selector; }

            private ForecastDisplayLevel Mask { get { return this._selector.Mask; } }

            public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
                int mask = (int)this.Mask;
                int level = (int)(ForecastDisplayLevel)value;

                int sliderValue = 0;

                if( (level & mask) != 0 )
                    ++sliderValue;

                level >>= 1;
                if( (level & mask) != 0 )
                    ++sliderValue;

                return (double)sliderValue;
            }

            public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
                int sliderValue = (int)Math.Round( (double)value );

                ForecastDisplayLevel displayLevel;
                switch( sliderValue ) {
                    case 0:
                    default:
                        displayLevel = ForecastDisplayLevel.None;
                        break;

                    case 1:
                        int level = 1;
                        int mask = (int)this.Mask;
                        while( (level & mask) == 0 )
                            level <<= 1;

                        displayLevel = (ForecastDisplayLevel)level;
                        break;

                    case 2:
                        displayLevel = this.Mask;
                        break;
                }

                return displayLevel;
            }
        }

    }

    /// <summary>Selects integer values on a slider.</summary>
    public partial class DisplaySelectorSlider : Slider {

        private double _limit = 1.0;
        public double Limit {
            get { return this._limit; }
            set {
                if( this._limit != value ) {
                    this._limit = value;
                    this.UpdateDisplayedLimit( this.Value );
                }
            }
        }


        public DisplaySelectorSlider( ) {
            this.MouseLeftButtonUp += OnMouseLeftButtonUp;
        }


        private void OnMouseLeftButtonUp( object sender, MouseButtonEventArgs e ) {
            this.Value = Math.Round( Value );
        }


        protected override void OnValueChanged( double oldValue, double newValue ) {
            double valueLimit = 2 * this.Limit;
            if( newValue > valueLimit ) {
                this.Value = valueLimit;
            }
            else {
                base.OnValueChanged( oldValue, newValue );
                this.UpdateDisplayedLimit( newValue );
            }
        }

        protected override void OnMaximumChanged( double oldMaximum, double newMaximum ) {
            base.OnMaximumChanged( oldMaximum, newMaximum );
            this.UpdateDisplayedLimit( this.Value );
        }

        private void UpdateDisplayedLimit( double value ) {
            double maximum = this.Maximum + 0.25;
            this.SmallChange =
                this.Limit < 1.0
                    ? this.Limit - value / maximum
                    : 1.0;
        }

    }

    /// <summary>Uses the source value as the relative offset of a linear opacity mask.</summary>
    public sealed class OpacityMaskConverter : IValueConverter {

        public double Angle { get; set; }

        public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
            double offset = (double)value;
            if( offset >= 1.0 )
                return null;

            var stops = new GradientStopCollection {
                new GradientStop{ Color = Colors.White, Offset = (double)value },
                new GradientStop{ Color = Colors.Transparent, Offset = (double)value }
            };
            var opacityMask = new LinearGradientBrush( stops, Angle );
            return opacityMask;
        }

        public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
            return DependencyProperty.UnsetValue;
        }

    }

    /// <summary>Determines whether a value is below (Collapsed) or above (Visible) a parameter limit.</summary>
    public sealed class LimitVisibilityConverter : IValueConverter {

        public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
            double limit = System.Convert.ToDouble( parameter ?? 1.0 );
            return (double)value < limit
                ? Visibility.Collapsed
                : Visibility.Visible;
        }

        public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
            return DependencyProperty.UnsetValue;
        }

    }

}
